package main;

import handlers.KeyHandler;

import java.net.URL;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import jmetest.renderer.TestSkybox;
import mapControl.GraphicsMapControl;
import modelLoader.ModelLoader;

import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Skybox;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.state.ZBufferState;
import com.jme.util.TextureManager;
import com.jmex.audio.AudioSystem;
import com.jmex.audio.AudioTrack;
import com.jmex.audio.AudioTrack.TrackType;
import com.jmex.audio.MusicTrackQueue.RepeatType;

import elementos.Floor;
import elementos.Player;
import enemies.Bad_Boys;
import enemies.Enemy;

/**
 * Clase principal
 * @author Sebastian Giulietti 69032 
 * @author Adrian Sanchez 64251
 */

public class JDM extends SimpleGame {


	
	/**
	 * Crea una nueva instancia del juego y la ejecuta
	 * @param args
	 */
	public static void main(String[] args) {
		Logger.getLogger("").setLevel(Level.WARNING); // to see the important
		JDM game = new JDM();
		game.start();
	}

	
	/**
	 * Audio 
	 */
	private AudioSystem audio;
	
	/**
	 * Camara 
	 */
	private ChaseCamera chaseCamera;
	
	/**
	 * Indice del objetivo Actual de la camara 
	 */
	private int indexObjAct;

	/**
	 * Piso 
	 */
	@SuppressWarnings("unused")
	private Floor floor;
	
	
	/**
	 * Maneja los enemigos 
	 */
	private Bad_Boys IA;
	
	
	
	/**
	 * Control del mapa y de los graficos 
	 */
	GraphicsMapControl mapControl;
	
	/**
	 * Bomberman 
	 */
	Player player;
	
	/**
	 * Fondo 
	 */
	private Skybox skybox;
	
	
	/**
	 * Recolector de nodos que se quieren eliminar, se utiliza para eliminar nodos solo del thread principal 
	 */
	Queue<Node> trashColector;

	/**
	 * Primera vida? hay que inicializar cosas 
	 */
	private boolean fstTime = true;

	private Text score_l;

	long totalTime ;

	private static final int MS_SECOND = 1000;

	private static final int MS_MINUTE = 60000;

	private static final int MS_HOUR = 3600000;

	private Text time;

	/**
	 * Inicializa una camara que flota detras de un objetivo 
	 */
	private void buildChaseCamera() {
		HashMap<String, Object> props = new HashMap<String, Object>();
		props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(10, 10,
				60 * FastMath.DEG_TO_RAD));
		props.put(ChaseCamera.PROP_DAMPINGK, "4");
		props.put(ChaseCamera.PROP_SPRINGK, "9");
		props.put(ThirdPersonMouseLook.PROP_ENABLED, "false");
		chaseCamera = new ChaseCamera(cam, player.getNode(), props);
		chaseCamera.setMaxDistance(80);
		chaseCamera.setMinDistance(60);
	}

	/**
	 * Inicializa el control de enemigos 
	 */
	private void buildIA() {
		IA = new Bad_Boys(mapControl, player);
		mapControl.setIA(IA);
	}

	/**
	 * Inicializa la entrada 
	 */
	private void buildInput() {
		input = new KeyHandler(player, mapControl);

		InputAction cambiarObjetivo = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					Spatial objetivo = chaseCamera.getTarget();

					if (objetivo == player.getNode()) {
						indexObjAct = 0;
						objetivo = IA.getCollectionEnemies().get(indexObjAct)
								.getNode();
					} else if (++indexObjAct < IA.getCollectionEnemies().size()) {

						objetivo = IA.getCollectionEnemies().get(indexObjAct)
								.getNode();

					} else {
						objetivo = player.getNode();
					}

					chaseCamera.setTarget(objetivo);
					System.out.println("obj: " + objetivo.getName());
				}

			}

		};

		InputAction farCam = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					chaseCamera
							.setMaxDistance(chaseCamera.getMaxDistance() + 10);
					chaseCamera
							.setMinDistance(chaseCamera.getMinDistance() + 10);
				}
			}
		};

		InputAction nearCam = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					chaseCamera
							.setMaxDistance(chaseCamera.getMaxDistance() - 10);
					chaseCamera
							.setMinDistance(chaseCamera.getMinDistance() - 10);

				}
			}
		};

		InputAction restart = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {

					if (mapControl.getGameOver()) {
						mapControl.setGameOver(false);
						rootNode.detachAllChildren();
						
						simpleInitGame();

					}

				}
			}
		};

		input.addAction(restart, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_R,
				InputHandler.AXIS_NONE, false);

		input.addAction(farCam, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_PGUP, InputHandler.AXIS_NONE, false);

		input.addAction(nearCam, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_PGDN, InputHandler.AXIS_NONE, false);

		input.addAction(cambiarObjetivo, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_DELETE, InputHandler.AXIS_NONE, false);

	}

	/**
	 * Agrega luces 
	 */
	private void buildLights() {
		lightState.detachAll();
		DirectionalLight dLight = new DirectionalLight();
		dLight.setEnabled(true);
		dLight.setDiffuse(new ColorRGBA(1, 1, 1, 1));
		dLight.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.7f, 1));
		dLight.setDirection(new Vector3f(-0.8f, -1f, -0.8f));
		lightState.attach(dLight);

	}

	/**
	 * buildSkyBox creates a new skybox object with all the proper textures. The
	 * textures used are the standard skybox textures from all the tests.
	 * 
	 */
	private void buildSkyBox() {
		skybox = new Skybox("skybox", 10, 10, 10);

		Texture north = TextureManager.loadTexture(
				TestSkybox.class.getClassLoader().getResource(
						"jmetest/data/texture/north.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture south = TextureManager.loadTexture(
				TestSkybox.class.getClassLoader().getResource(
						"jmetest/data/texture/south.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture east = TextureManager.loadTexture(TestSkybox.class
				.getClassLoader().getResource("jmetest/data/texture/east.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture west = TextureManager.loadTexture(TestSkybox.class
				.getClassLoader().getResource("jmetest/data/texture/west.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture up = TextureManager.loadTexture(TestSkybox.class
				.getClassLoader().getResource("jmetest/data/texture/top.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		Texture down = TextureManager.loadTexture(TestSkybox.class
				.getClassLoader()
				.getResource("jmetest/data/texture/bottom.jpg"),
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);

		skybox.setTexture(Skybox.Face.North, north);
		skybox.setTexture(Skybox.Face.West, west);
		skybox.setTexture(Skybox.Face.South, south);
		skybox.setTexture(Skybox.Face.East, east);
		skybox.setTexture(Skybox.Face.Up, up);
		skybox.setTexture(Skybox.Face.Down, down);
		skybox.preloadTextures();
		rootNode.attachChild(skybox);
	}

	/**
	 * Carga la pista de audio 
	 * @param resource URL De la pista
	 * @return loop de audio
	 */
	private AudioTrack getMusic(URL resource) {
		AudioTrack sound = AudioSystem.getSystem().createAudioTrack(resource,
				true);
		sound.setType(TrackType.MUSIC);
		sound.setRelative(true);
		sound.setTargetVolume(0.7f);
		sound.setLooping(true);
		return sound;
	}

	/**
	 * Inicializa el sistema de sonido
	 */
	private void initSound() {
		if (audio == null) {
			// grab a handle to our audio system.
			audio = AudioSystem.getSystem();

			// setup our ear tracker to track the camera's position and
			// orientation.
			audio.getEar().trackOrientation(player.getNode());
			audio.getEar().trackPosition(player.getNode());

			// setup a music score for our demo
			AudioTrack music1 = getMusic(getClass().getResource(
					"/music/loop.ogg"));
			audio.getMusicQueue().setRepeatType(RepeatType.ALL);
			audio.getMusicQueue().setCrossfadeinTime(5f);
			audio.getMusicQueue().setCrossfadeoutTime(5f);
			audio.getMusicQueue().addTrack(music1);
			audio.getMusicQueue().play();
		}
	}

	/* (non-Javadoc)
	 * @see com.jme.app.BaseSimpleGame#simpleInitGame()
	 */
	@Override
	protected void simpleInitGame() {
		// En la primera pasada
		if (fstTime ){
 
		ModelLoader.LoadModels(); // Prepara los modelos

		// optimizacion
		ZBufferState buf = display.getRenderer().createZBufferState();
		buf.setEnabled(true);
		buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
		rootNode.setRenderState(buf);

		// Inicia el recolector de nodos basura 
		trashColector = new ConcurrentLinkedQueue<Node>();
		

		// Inicia el fondo
		buildSkyBox();

		//Timer
		time = new Text("text", "00:00");
		score_l = new Text("score", "000");
		// Un nuevo control grafico del mapa
		mapControl = new GraphicsMapControl(21, 21, 5, rootNode, display,
				trashColector);
		
		// Ya no es la primera pasada
		fstTime = false;
		}
		mapControl.resetearPuntos();

		/// Timer
		
		totalTime = System.currentTimeMillis();
			
		time = Text.createDefaultTextLabel("Text Label", "00:00");
		time.setLocalTranslation(new Vector3f(1, 60, 0));	
		rootNode.attachChild(time);	

		score_l = Text.createDefaultTextLabel("Score Label", "0");
		score_l.setLocalTranslation(new Vector3f(1, 40, 0));	
		rootNode.attachChild(score_l);	

		// Nueva vida
		mapControl.setGameOver(false);
		
		// Arma el mapa
		mapControl.buildMap();
		
		// Pone el piso
		floor = new Floor(mapControl);

		// pone al jugador
		player = new Player(mapControl);

		// asigna las teclas de entrada 
		buildInput();
		
		// Luces
		buildLights();
		
		// Camara 
		buildChaseCamera();
		
		// Enemigos 
		buildIA();

		// Musicausic
		initSound();
		
		// Actualiza el esenario
		rootNode.updateGeometricState(0.0f, true);
		rootNode.updateRenderState();

	//	mapControl.setGodMode(true);
	}
	/** Convierte un tiempo en ms a horas, minutos y segundos
	 * @param durationMS tiempo
	 * @return formato entendible
	 */
	public static String getDuration(long durationMS) {
		 long tmpTime = durationMS;
		 long hours = tmpTime / MS_HOUR;
		 tmpTime -= hours * MS_HOUR;
		 long minutes = tmpTime / MS_MINUTE;
		 tmpTime -= minutes * MS_MINUTE;
		 long seconds = tmpTime / MS_SECOND;
		 tmpTime -= seconds * MS_SECOND;
		 return hours + ":" + minutes + ":" + seconds + "." + tmpTime;
		 }
	
	/* (non-Javadoc)
	 * @see com.jme.app.BaseSimpleGame#simpleUpdate()
	 */
	@Override
	protected void simpleUpdate() {
		mapControl.getRootNode().updateRenderState();
		 time.print("Time: " +getDuration(System.currentTimeMillis() - totalTime));
		 score_l.print(String.valueOf(mapControl.getScore()));
		 if (System.currentTimeMillis() - mapControl.getGodTime() > 5000)
		 {
			 mapControl.setGodMode(false);
		 }
		 
		 
		 // actualiza la posicion de la camara
		chaseCamera.update(tpf);
		
		// sigo vivo? 
		if (!mapControl.getGameOver()) {

			// muevo el fondo
			skybox.setLocalTranslation(cam.getLocation());
			
			// saco la basura
			if (!trashColector.isEmpty()) {
				rootNode.detachChild(trashColector.remove());
			}

			// me estoy moviendo? sino respiro
			if (ModelLoader.getPlayerController()
					.getActiveAnimation().isCyleComplete()) {
				ModelLoader.getPlayerController().fadeTo("idle", 1,
						false);
				ModelLoader.getPlayerController().setSpeed(1);

			}

			// actulizacion de audio
			audio.update();
		
			// actualizacion del jugador
			player.update();
			
			// actualizacion del esenario
			rootNode.updateGeometricState(tpf, true);
		} else {
			// Esta muerto el bomberman saco todo lo que hay en el mapa
			if (!mapControl.getIA().getCollectionEnemies().isEmpty()) {
				Queue<Enemy> temp = new ConcurrentLinkedQueue<Enemy>();
				for (Enemy e : mapControl.getIA().getCollectionEnemies()) {
					temp.add(e);
				}
				while (!temp.isEmpty()) {
					temp.remove().kill();
				}
			}
			floor = null;
			player = null;
			rootNode.detachAllChildren();
			rootNode.attachChild(new Text("game Over",
					"Game Over - Press \"R\" for restart."));
		}

	}
}